package cn.tangcent;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
 * Created by TangMing on 2017/4/8.
 */
public class SudokuUtils {
    private static List<Integer> SINGLES = new ArrayList<>();
    private static List<Integer> UNIQUES = new ArrayList<>();
    public static int ALL = 0b111111111;

    static {
        for (int i = 0; i < 9; i++) {
            int box = 1 << i;
            SINGLES.add(box);
            UNIQUES.add(ALL & ~box);
        }
    }

    public static boolean all(int i) {
        return ALL == i;
    }

    public static int box(int i) {
        if (i < 0 || i > 9) {
            throw new IllegalArgumentException("unexpected val:" + i);
        }
        return 1 << (i - 1);
    }

    public static int unbox(int i) {
        return SINGLES.indexOf(i) + 1;
    }

    public static boolean unique(int i) {
        return UNIQUES.contains(i);
    }

    public static boolean single(int i) {
        return SINGLES.contains(i);
    }

    public static int uniqueIndex(int i) {
        return UNIQUES.get(i);
    }

    public static int unique2Single(int i) {
        return 1 << UNIQUES.get(i);
    }

    public static int toZ(Coordinate coordinate) {
        return toZ(coordinate.getX(), coordinate.getY());
    }

    public static int toZOrder(Coordinate coordinate) {
        int z = toZ(coordinate);
        return ((coordinate.getX() - (z / 3) * 3) * 3) + (coordinate.getY() - (z % 3) * 3);
    }

    public static int toZ(int x, int y) {
        return x / 3 * 3 + y / 3;
    }

    public static Coordinate z2XY(int z, int order) {
        return Coordinate.instance(((z / 3) * 3 + order / 3), ((z % 3) * 3 + order % 3));
    }

    public static void forZ(int z, BiConsumer<Coordinate, Integer> consumer) {
        int xShift = (z / 3) * 3;
        int yShift = (z % 3) * 3;
        int order = 0;
        for (int x = 0; x < 3; x++) {
            for (int y = 0; y < 3; y++) {
                consumer.accept(Coordinate.instance(x + xShift, y + yShift), order++);
            }
        }
    }

    public static int toIndex(Coordinate coordinate) {
        return toIndex(coordinate.getX(), coordinate.getY());
    }

    public static int toIndex(int x, int y) {
        return (x << 4) + y;
    }

    private static int mask = 0b1111;

    public static Coordinate index2XY(int index) {
        return Coordinate.instance((index >> 4) & mask, index & mask);
    }

    public static int xOfIndex(int index) {
        return (index >> 4) & mask;
    }

    public static int yOfIndex(int index) {
        return index & mask;
    }

    public static int shift(int num, int shift) {
        num += shift;
        return num > 8 ? num - 9 : num;
    }

    public static int inversion(int num) {
        return ALL & ~num;
    }

    public static int oneBits(int num) {
        int count = 0;
        while (num != 0) {
            num = (num - 1) & num;
            count++;
        }
        return count;
    }

    public static void forBit(int num, Consumer<Integer> consumer) {
        for (int i = 0; i < 9; i++) {
            if ((SINGLES.get(i) & num) != 0) {
                consumer.accept(i);
            }
        }
    }

    public static int[][] string2Sudoku(String eles) {
        int[][] elements = new int[9][9];
        each(elements, ((val, x, y) -> char2Num(eles.charAt(x * 9 + y))));
        return elements;
    }

    private static int char2Num(char ch) {
        return ch - '0';
    }

    public static void sibling(Coordinate coordinate, Consumer<Coordinate> consumer) {
        sibling(coordinate.getX(), coordinate.getY(), consumer);
    }

    public static void sibling(int x, int y, Consumer<Coordinate> consumer) {
        Set<Integer> cache = new HashSet<>();
        cache(cache, SudokuUtils.toIndex(x, y));
        for (int i = 0; i < 9; i++) {
            if (i != x && cache(cache, SudokuUtils.toIndex(i, y))) {
                consumer.accept(Coordinate.instance(i, y));
            }
            if (i != y && cache(cache, SudokuUtils.toIndex(x, i))) {
                consumer.accept(Coordinate.instance(x, i));
            }
        }
        SudokuUtils.forZ(SudokuUtils.toZ(x, y), (coordinate, order) -> {
            if (coordinate.not(x, y) && cache(cache, SudokuUtils.toIndex(coordinate.getX(), coordinate.getY()))) {
                consumer.accept(coordinate);
            }
        });
    }

    public static boolean cache(Set<Integer> cache, Integer val) {
        if (cache.contains(val)) {
            return false;
        } else {
            cache.add(val);
            return true;
        }
    }

    public static void each(int[][] elements, EleCall eleCall) {
        for (int x = 0; x < elements.length; x++) {
            for (int y = 0; y < elements[x].length; y++) {
                elements[x][y] = eleCall.process(elements[x][y], x, y);
            }
        }
    }

    public static interface EleCall {
        int process(int val, int x, int y);
    }

    public static boolean sameRow(Coordinate one, Coordinate another) {
        return one.getX() == another.getX();
    }

    public static boolean sameColumn(Coordinate one, Coordinate another) {
        return one.getY() == another.getY();
    }

    public static boolean sameBlock(Coordinate one, Coordinate another) {
        return toZ(one) == toZ(another);
    }

    private static boolean enableLog = false;

    public static void enableLog(boolean enableLog) {
        SudokuUtils.enableLog = enableLog;
    }

    public static void print(String msg) {
        if (enableLog) {
            System.out.print(msg);
        }
    }

    public static void print(int[][] result) {
        for (int x = 0; x < 9; x++) {
            if (x % 3 == 0) {
                System.out.print("\n");
            }
            System.out.print("\n");
            for (int y = 0; y < 9; y++) {
                if (y % 3 == 0) {
                    System.out.print("\t");
                }
                System.out.print(result[x][y]);
            }
        }
    }

    public static Coordinate[] entireRow(int row) {
        Coordinate[] result = new Coordinate[9];
        for (int i = 0; i < 9; i++) {
            result[i] = Coordinate.instance(row, i);
        }
        return result;
    }

    public static Coordinate[] entireColumn(int column) {
        Coordinate[] result = new Coordinate[9];
        for (int i = 0; i < 9; i++) {
            result[i] = Coordinate.instance(i, column);
        }
        return result;
    }

    public static Coordinate[] entireBlock(int z) {
        Coordinate[] result = new Coordinate[9];
        for (int i = 0; i < 9; i++) {
            result[i] = z2XY(z, i);
        }
        return result;
    }
}
